Skip to main content
Version: v5

5.x Upgrade Guide

Breaking changes

There were several significant breaking changes in RJSF version 5 that were necessary in order to support the following new features:

  • Schema validation was decoupled from @rjsf/core to resolve issue #2693. Additionally, in order to break a circular dependency in the validation refactor, the @rjsf/core/utils.js file was split out into its own @rjsf/utils package as was suggested in #1655.
  • The theme for Material UI version 5 (i.e. @rjsf/mui) was split out of the theme for version 4 (i.e. @rjsf/material-ui) to resolve the following issues: #2762, #2858, #2905, #2945
  • As part of the fix for #2526 all the existing templates in the previous version were moved into a new templates dictionary, similar to how widgets and fields work. This templates dictionary was added to the Registry and also the Form props, replacing the ArrayFieldTemplate, FieldTemplate, ObjectFieldTemplate and ErrorList props. In addition, several of the fields and widgets based components were moved into the templates dictionary as they were more like templates than true Fields or Widgets. Also fixes #2945
  • Fixed anyOf and oneOf getting incorrect, potentially duplicate ids when combined with array #2197
  • CheckboxesWidget, RadioWidget and SelectWidget in all themes now use indexes into enumOptions[] rather than option.value to allow for objects in enumOptions - #1494

Node support

Version 5 is dropping official support for Node 12 as it is no longer a maintained version. Please use Node 18 when making any changes to package.json and package-lock.json files. All PR and branch builds are running against Node 14, 16 and 18.

React version

RJSF is no longer actively supporting React version < 16.14.x. React 17 is officially supported on all the themes where the underlying theme library also supports React 17.

Unfortunately, there is required work pending to properly support React 18, so use it at your own risk.

New packages

There are four new packages added in RJSF version 5:

  • @rjsf/utils: All of the utility functions previously imported from @rjsf/core/utils as well as the Typescript types for RJSF version 5.
    • The following new utility functions were added: ariaDescribedByIds(), createSchemaUtils(), descriptionId(), enumOptionsDeselectValue(), enumOptionsIndexForValue(), enumOptionsIsSelected(), enumOptionsSelectValue(), enumOptionsValueForIndex(), errorId(), examplesId(), getClosestMatchingOption(), getFirstMatchingOption(), getInputProps(), helpId(), mergeValidationData(), optionId(), sanitizeDataForNewSchema() and titleId()
  • @rjsf/validator-ajv6: The ajv-v6-based validator refactored out of @rjsf/core@4.x, that implements the ValidatorType interface defined in @rjsf/utils.
  • @rjsf/validator-ajv8: The ajv-v8-based validator that is an upgrade of the @rjsf/validator-ajv6, that implements the ValidatorType interface defined in @rjsf/utils. See the ajv 6 to 8 migration guide for more information.
  • @rjsf/mui: Previously @rjsf/material-ui/v5, now provided as its own theme.

id BREAKING CHANGES

In many of the themes the ids for the Title, Description and Examples blocks were update to all have a consistent value of xxx__title, xxx__description and xxx__examples, respectively, where xxx is the id of the field. In addition, some of the ids for various input values were updated to be consistent across themes or to fix small bugs. For instance, the values for radio buttons in the RadioWidget and checkboxes in the CheckboxesWidget are of the form xxx-${option.value}, where xxx is the id of the field.

enumOptions[] widgets BREAKING CHANGES

Schemas may have enumOptions[] where values are objects rather than primitive types. Examples of such schemas have been added to the playground. These schemas did not work in RJSF prior to v5. In every theme, the enumOptions[] rendering widgets CheckboxesWidget, RadioWidget and SelectWidget previously used the enumOptions[].value to as the value used for the underlying checkbox, radio and select.option elements. Now, these CheckboxesWidget, RadioWidget and SelectWidget components use the index of the enumOptions[] in the list as the value for the underlying elements. If you need to build a custom widget for this kind of enumOptions, there are a set of enumOptionsXXX functions in @rjsf/utils to support your implementation.

@rjsf/core BREAKING CHANGES

Types

In version 4, RJSF exported all its types directly from @rjsf/core. In version 5, only the types for the Form component and the withTheme() HOC are exported directly from @rjsf/core. All the rest of the types for RJSF are now exported from the new @rjsf/utils package.

NOTE: The types in @rjsf/utils have been improved significantly from those in version 4 and as a result may require you to fix your Typescript typings and add some casts.

Some of the most notable changes are:

  • RJSFSchema has replaced the use of JSON7Schema for future compatibility reasons.

    • Currently RJSFSchema is simply an alias to JSON7Schema so this change is purely a naming one.
    • It is highly recommended to update your use of JSON7Schema with RJSFSchema so that when the RJSF begins supporting a newer JSON Schema version out-of-the-box, your code won't be affected.
  • RJSFSchemaDefinition has replaced the use of JSONSchema7Definition for the same reasons.

  • The use of the generic T (defaulting to any) for the formData type has been expanded to cover all type hierarchies that use formData.

  • StrictRJSFSchema and RJSFSchema have replaced the use of JSON7Schema for future compatibility reasons.

    • RJSFSchema is StrictRJSFSchema joined with the GenericObjectType (i.e. { [key: string]: any }) to allow for additional syntax related to newer draft versions
    • All definitions of schema and rootSchema elements have been replaced with a generic that is defined as S extends StrictRJSFSchema = RJSFSchema
    • It is highly recommended to update your use of JSON7Schema with RJSFSchema since that is the default for the new generic used for schema and rootSchema
  • A new generic F (extending FormContextType defaulting to any) was added for the formContext type, and all types in the hierarchy that use formContext have had that generic added to them.

  • The new CustomValidator, ErrorTransformer, ValidationData, ValidatorType and SchemaUtilsType types were added to support the decoupling of the validation implementation.

  • The new TemplatesType, ArrayFieldDescriptionProps, ArrayFieldTitleProps, UnsupportedFieldProps, IconButtonProps, SubmitButtonProps and UIOptionsBaseType were added to support the consolidation (and expansion) of templates in the Registry and Form.

  • BREAKING CHANGE The DescriptionField and TitleField props were removed from the ArrayFieldTemplateProps and ObjectFieldTemplateProps as they can now be derived from the templates or uiSchema via the new getTemplate() utility function.

  • BREAKING CHANGE The fields prop was removed from the FieldTemplateProps as you can simply use registry.fields instead.

  • BREAKING CHANGE The showErrorList prop was changed to accept false, "top" or "bottom". true is no longer a valid value. The default value is "top", which has identical behavior to the default value/true in v4. You can view all these types on GitHub.

Form props

In version 5, the Form component's two optional props additionalMetaSchemas and customFormats were replaced with the new, required validator prop, in order to support the decoupling of the validation implementation. This new validator prop is expected to be an implementation of the ValidatorType interface. The new @rjsf/validator-ajv6 package contains the refactored implementation of the version 4 validator; It was provided for backwards compatibility with RJSF v4, and it is deprecated.
The new @rjsf/validator-ajv8 package contains the refactored implementation of the version 4 validator, that has been converted to use the Ajv 8 validator and has more capabilities than the Ajv 6 one. See the Ajv migration guide for more information.

There are two ways to use this new package to provide a validator for a Form. First, you can simply import the default validator from the package and pass it to a Form.

import { RJSFSchema } from "@rjsf/utils";
import Form from "@rjsf/core";
import validator from "@rjsf/validator-ajv8";

// Your schema
const schema: RJSFSchema = { ... };

render((
<Form schema={schema} validator={validator} />
), document.getElementById("app"));

Second, if you were actually providing one (or both) of the removed optional props to your Form, you can continue using them by creating a customized validator.

import { RJSFSchema } from "@rjsf/utils";
import Form from "@rjsf/core";
import { customizeValidator, CustomValidatorOptionsType } from "@rjsf/validator-ajv8";

// Your schema, additionalMetaSchemas and/or customFormats
const schema: RJSFSchema = { ... };
const additionalMetaSchemas: CustomValidatorOptionsType['additionalMetaSchemas'] = [{ ... }];
const customFormats: CustomValidatorOptionsType['customFormats'] = { ... };

const validator = customizeValidator({ additionalMetaSchemas, customFormats });

render((
<Form schema={schema} validator={validator} />
), document.getElementById("app"));
formElement converted to RefObject

The formElement variable that stored the ref to the inner <form /> was converted from a simple variable assigned via a callback ref (ala React < 16.3) to a React.RefObject created using the React.createRef() API. As a result, if you were using the formElement ref, you will need to update it to use formElement.current:

import { RJSFSchema } from "@rjsf/utils";
import Form from "@rjsf/core";
import validator from "@rjsf/validator-ajv8";

// Your schema
const schema: RJSFSchema = { ... };

const formRef = React.createRef();

render((
<Form ref={formRef} schema={schema} validator={validator} />
), document.getElementById("app"));

...
// Previously, to reset the form one would have called:
// formRef.current.formElement.reset();
// Now one calls:
formRef.current.formElement.current.reset();
validate prop renamed

Additionally, in version 5, the validate prop on Form was renamed to customValidate to avoid confusion with the new validator prop.

fields prop changes

In previous versions, it was possible to provide an override to the DescriptionField, TitleField and/or UnsupportedField components by providing a custom implementation in the fields prop on the Form. Since these components do not actually support the FieldProps interface, they were moved into the templates dictionary instead. If you were previously overriding any (or all) of these components, you can override them now via the templates prop on Form instead:

import { DescriptionFieldProps, RJSFSchema, TitleFieldProps } from "@rjsf/utils";
import Form from "@rjsf/core";
import validator from "@rjsf/validator-ajv8";

// Your schema
const schema: RJSFSchema = { ... };

// Your custom fields
const CustomDescriptionField = (props: DescriptionFieldProps) => { ... };
const CustomTitleField = (props: TitleFieldProps) => { ... };
const CustomUnsupportedField = (props: ObjectFieldTemplateProps) => { ...
};

const templates: Partial<TemplatesType> = {
DescriptionFieldTemplate: CustomDescriptionField,
TitleFieldTemplate: CustomTitleField,
UnsupportedFieldTemplate: CustomUnsupportedField,
};

render((
<Form schema={schema} validator={validator} templates={templates}/>
), document.getElementById("app"));
new templates prop

Additionally, in version 5, the ArrayFieldTemplate, FieldTemplate, ObjectFieldTemplate and ErrorList props were replaced with the templates prop as part of the TemplatesType consolidation. If you were previously overriding any (or all) of these templates, you can simply consolidate them into the new templates prop on Form instead:

import { ArrayFieldTemplateProps, ErrorListProps, FieldTemplateProps, ObjectFieldTemplateProps, RJSFSchema } from "@rjsf/utils";
import Form from "@rjsf/core";
import validator from "@rjsf/validator-ajv8";

// Your schema
const schema: RJSFSchema = { ... };

// Your custom templates
const CustomArrayFieldTemplate = (props: ArrayFieldTemplateProps) => { ... };
const CustomFieldTemplate = (props: FieldTemplateProps) => { ... };
const CustomObjectFieldTemplate = (props: ObjectFieldTemplateProps) => { ... };
const CustomErrorField = (props: ErrorListProps) => { ... };

const templates: Partial<TemplatesType> = {
ArrayFieldTemplate: CustomArrayFieldTemplate,
FieldTemplate: CustomFieldTemplate,
ObjectFieldTemplate: CustomObjectFieldTemplate,
ErrorFieldTemplate: CustomErrorField,
};

render((
<Form schema={schema} validator={validator} templates={templates} />
), document.getElementById("app"));

NOTE: In version 5, the ArrayField implementation was refactored to add 3 additional templates for presenting arrays along with the ArrayFieldTemplate. If you were updating the ArrayFieldTemplate to modify just a subset of the UI, it may be easier for you to implement one of the other new templates instead. See the Custom Templates documentation for more details.

widgets prop change

In the previous version, it was possible to provide an override to the SubmitButton component by providing a custom implementation in the widgets prop on the Form. Since this component only requires a tiny fraction of the WidgetProps interface, it was moved into the templates.ButtonTemplates dictionary instead with its own, reduced set of props. If you were previously overriding this component, you can override it now via the templates prop on Form instead:

import { RJSFSchema, SubmitButtonProps } from "@rjsf/utils";
import Form from "@rjsf/core";
import validator from "@rjsf/validator-ajv8";

// Your schema
const schema: RJSFSchema = { ... };

// Your custom button
const CustomSubmitButton = (props: SubmitButtonProps) => { ...
};

const templates: Partial<TemplatesType> = {
ButtonTemplates: {
SubmitButton: CustomSubmitButton,
}
};

render((
<Form schema={schema} validator={validator} templates={templates}/>
), document.getElementById("app"));

utils.js

In version 5, all the utility functions that were previously accessed via import { utils } from '@rjsf/core'; are now available via import utils from '@rjsf/utils';. Because of the decoupling of validation from @rjsf/core there is a breaking change for all the validator-based utility functions, since they now require an additional ValidatorType parameter. More over, one previously exported function resolveSchema() is no longer exposed in the @rjsf/utils, so use retrieveSchema() instead. Finally, the function getMatchingOption() has been deprecated in favor of getFirstMatchingOption().

If you have built custom fields or widgets that utilized any of these breaking-change functions, don't worry, there is a quick and easy solution for you. The registry has a breaking-change which removes the previously deprecated definitions property while adding the new schemaUtils property. This new registry.schemaUtils property implements the SchemaUtilsType interface, which allows you to call a version of each of these breaking-change functions without the need for passing either a validator or rootSchema. Because all fields and widgets are guaranteed to be passed the registry as a prop, if your custom field/widget happens to use either the registry.definitions object or a breaking-change validator-based utility function you make the following changes:

import { RJSFSchema, FieldProps } from '@rjsf/utils';

function YourField(props: FieldProps) {
const { registry } = props;
// Change `registry.definitions` to `registry.rootSchema.definitions`
// const { definitions } = registry; <- version 4
const { rootSchema } = registry;
const { definitions } = rootSchema;
...
}
// Change breaking-change function to schemaUtils instead, otherwise import from @rjsf/utils
// import { utils } from '@rjsf/core'; <- version 4
// const { isMultiSelect, resolveSchema, getUiOptions } = utils; <- version 4
import { RJSFSchema, WidgetProps, getUiOptions } from '@rjsf/utils';

function YourWidget(props: WidgetProps) {
const { registry, uiSchema } = props;
const { schemaUtils } = registry;
// const matchingOption = getMatchingOption({}, options, rootSchema); <- version 4
// const isMultiSelect = isMultiSelect(schema, rootSchema); <- version 4
// const newSchema = resolveSchema(schema, formData, rootSchema); <- version 4
const matchingOption = schemaUtils.getFirstMatchingOption({}, options);
const isMultiSelect = schemaUtils.isMultiSelect(schema);
const newSchema: RJSFSchema = schemaUtils.retrieveSchema(schema, formData);
const options = getUiOptions(uiSchema);

...
}

validator.js

Because of the decoupling of validation from @rjsf/core this file was refactored into its own @rjsf/validator-ajv8 package. During that refactor a few breaking changes were made to how it works related to custom validation and ErrorSchema conversion.

toErrorList param changed

In previous versions, the toErrorList() function used to take a fieldName string defaulted to root, and used it to format the stack message. In version 5, fieldName was changed to fieldPath string array defaulted to an empty array, and is used to recursively add the field name to the errors as the property object along with the raw message. The result is that if you had an ErrorSchema that looks like:

const errorSchema: ErrorSchema = {
__errors: ['error message 1'],
password: { __errors: 'passwords do not match' },
};

The returned result from calling toErrorList(errorSchema) has changed as follows:

// version 4 result
[{ stack: 'root: error message 1' }, { stack: 'password: passwords do not match' }][
// version 5 result
({ property: '.', message: 'error message 1', stack: '. error message 1' },
{
property: '.password',
message: 'passwords do not match',
stack: '.password passwords do not match',
})
];
Custom validation and extraErrors

In previous versions, when using a custom validator on the Form, any errors that were generated were inconsistently inserted into the validations errors list. In addition, there was an issue where the non-stack AJV error information was lost when custom validation generated errors. This issue has been fixed. Also, when extraErrors were provided, they were being inconsistently inserted into the errors list and the non-stack AJV error information was lost. In version 5, all of these errors will be consistently appended onto the end of the validation errors list, and the additional AJV error information is maintained.

In other words, if custom validation or extraErrors produced the following ErrorSchema:

{
__errors: [ "Please correct your password"],
password2: { __errors: "passwords do not match" }
}

And the AJV validation produced the following ErrorSchema:

{
__errors: [
{
message: 'should NOT be shorter than 3 characters',
name: 'minLength',
params: { limit: 3 },
property: '.password2',
schemaPath: '#/properties/password2/minLength',
stack: '.password2 should NOT be shorter than 3 characters',
},
];
}

The resulting errors list has changed as follows:

// version 4
[
{ stack: 'root: Please correct your password' },
{ stack: 'password2: passwords do not match' },
{ stack: 'password2: should NOT be shorter than 3 characters' },
][
// version 5
({
message: 'should NOT be shorter than 3 characters',
name: 'minLength',
params: { limit: 3 },
property: '.password2',
schemaPath: '#/properties/password2/minLength',
stack: '.password2 should NOT be shorter than 3 characters',
},
{
property: '.',
message: 'Please correct your password',
stack: '. Please correct your password',
},
{
property: '.',
message: 'passwords do not match',
stack: '.password2 passwords do not match',
})
];

Generate correct ids when arrays are combined with anyOf/oneOf

In v4, with arrays inside anyOf or oneOf, the parent name was not used to generate ids. For example, given a schema such as:

{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"anyOf": [
{
"properties": {
"foo": {
"type": "string"
}
}
},
{
"properties": {
"bar": {
"type": "string"
}
}
}
]
}
}
}
}

We would get fields with id root_foo and root_bar. As you can imagine, we could end up with duplicated ids if there's actually a foo or a bar in the root of the schema.

From v5, the child fields will correctly use the parent id when generating its own id, generating ids such as root_items_0_foo.

Deprecations added in v5

getMatchingOption()

The utility function getMatchingOption() was deprecated in favor of the more aptly named getFirstMatchingOption() which has the exact same implementation.

Non-standard enumNames property

enumNames is a non-standard JSON Schema field that was deprecated in version 5. enumNames could be included in the schema to apply labels that differed from an enumeration value. This behavior can still be accomplished with oneOf or anyOf containing const values, so enumNames support may be removed from a future major version of RJSF. For more information, see #532.

uiSchema.classNames

In versions previous to 5, uiSchema.classNames was the only property that did not require the ui: prefix. Additionally, it did not support being added into the ui:options object. This was fixed in version 5 to be consistent with all the other properties in the uiSchema, so the uiSchema.classNames support may be removed from a future major version of RJSF.

If you are using classNames as follows, simply add the ui: prefix to it to remove the deprecation warning that will be displayed for each uiSchema.classNames you have:

import { UiSchema } from '@rjsf/utils';

// This uiSchema will log a deprecation warning to the console
const uiSchemaLog: UiSchema = {
title: {
classNames: 'myClass',
},
};
// This uiSchema will not
const uiSchemaNoLog: UiSchema = {
title: {
'ui:classNames': 'myClass',
},
};

@rjsf/material-ui BREAKING CHANGES

This theme was simplified back to working only with Material UI version 4 after an unsuccessful attempt to have it fully support both versions 4 and 5 simultaneously. As a result, the MuiComponentContext, MuiForm5, Theme5 components and the useMuiComponent hook were removed from the export. In addition, the /v4 and /v5 sub-packages were also removed.

Migrating for Material UI version 4

If you were using this theme for Material UI version 4 AND you were using the sub-package, simply remove the /v4 from your imports.

If you modified your Typescript configuration for the /v4 sub-package, remove the following from your tsconfig.json:

{
...
"compilerOptions": {
...
"baseUrl": ".",
"paths": {
"@rjsf/material-ui/*": ["node_modules/@rjsf/material-ui/dist/*"]
}
}
}

If you modified your Jest configuration for the /v4 sub-package, remove the following from your jest.config.json:

  "moduleNameMapper": {
"@rjsf/material-ui/v4": "<rootDir>/node_modules/@rjsf/material-ui/dist/v4.js"
},

Migrating for Material UI version 5

If you were using this theme for Material UI version 5, you will want to use @rjsf/mui instead. See below for some before and after examples.

If you modified your Typescript configuration for the /v5 sub-package, remove the following from your tsconfig.json:

{
...
"compilerOptions": {
...
"baseUrl": ".",
"paths": {
"@rjsf/material-ui/*": ["node_modules/@rjsf/material-ui/dist/*"]
}
}
}

If you modified your Jest configuration for the /v5 sub-package, remove the following from your jest.config.json:

  "moduleNameMapper": {
"@rjsf/material-ui/v5": "<rootDir>/node_modules/@rjsf/material-ui/dist/v5.js"
},
Before
import Form5 from '@rjsf/material-ui';

or

import Form from '@rjsf/material-ui/v5';

or

import { withTheme } from '@rjsf/core';
import { Theme } from '@rjsf/material-ui/v5';
// Make modifications to the theme with your own fields and widgets
const Form = withTheme(Theme);

or

import { withTheme } from '@rjsf/core';
import { Theme as Theme5 } from '@rjsf/material-ui';
// Make modifications to the theme with your own fields and widgets
const Form = withTheme(Theme5);
After
import Form from '@rjsf/mui';

or

import { withTheme } from '@rjsf/core';
import { Theme } from '@rjsf/mui';
// Make modifications to the theme with your own fields and widgets
const Form = withTheme(Theme);